-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local m = {}
local log = require 'mc.logging'
local context = require 'mc.context'
local mdb_service = require 'mc.mdb.mdb_service'

local TPCM_MICROCOMPONENT_PATH <const> = '/bmc/kepler/tpcm/MicroComponent'
local MDB_SERVICE_PATH <const> = '/bmc/kepler/MdbService'
local MDB_INTF <const> = 'bmc.kepler.Mdb'
local COMPONENT_MDB_INTF <const> = 'bmc.kepler.Systems.TPCM.Component'
local MEASURED_MDB_INTF <const> = 'bmc.kepler.Managers.TPCM.Measured'
local FRUCTRL_MDB_INTF <const> = 'bmc.kepler.Systems.FruCtrl'
local VIRTUALMACHINE_PATH <const> = '/bmc/kepler/Systems/1/VirtualMachines'
local VIRTUALMACHINE_MDB_INTF <const> = 'bmc.kepler.Systems.VirtualMachine'
local VIRTUALMACHINE_TPCM_MDB_INTF <const> = 'bmc.kepler.Systems.VirtualMachine.TPCM'
local TPCM_MEASURED_MDB_INTF = 'bmc.kepler.Managers.TPCM.Measured'

function m.get_version_str(version)
    local t = {}
    for i = 0, 3 do
        t[i + 1] = (version & ((0xff) << (i*8))) >> (i*8)
    end
    return string.format("%d.%d.%d.%d", t[1], t[2], t[3], t[4])
end

local TCM_ALL_MEMBER_MAP = {
    [0x1b4e] = "国民技术",
    [0x0] = "未知厂商"
}

function m.get_tcm_manufacturer(manufacturer_id)
    if TCM_ALL_MEMBER_MAP[manufacturer_id] == nil then
        return TCM_ALL_MEMBER_MAP[0];
    end

    return TCM_ALL_MEMBER_MAP[manufacturer_id];
end

function m.check_power_state(system_id)
    local path = '/bmc/kepler/Systems/'.. tostring(system_id) ..'/FruCtrl'
    local ok, frulist = pcall(mdb.get_sub_objects, bus, path, FRUCTRL_MDB_INTF)
    if not ok then
        log:error("FruCtrl is not configured")
        return false
    end
    for _, fruobj in pairs(frulist) do
        if fruobj.PowerState ~= 'ON' then
            error(base_messages.ActionNotSupported('ResetComponentStandardDigest'))
        end
    end
    return true
end

local function get_component_detail(path)
    local detail = cjson.json_object_new_object()
    local component_obj = mdb.get_object(bus, path, COMPONENT_MDB_INTF)
    local measured_obj = mdb.get_object(bus, path, MEASURED_MDB_INTF)

    detail["Name"] = measured_obj.Name
    detail["ActualDigest"] = measured_obj.ActualDigest
    detail["ActualIdentifier"] = string.format('%s:%s', component_obj.ActualModel,
        component_obj.ActualUniqueIdentifier)
    detail["StandardDigest"] = measured_obj.StandardDigest
    detail["StandardIdentifier"] = string.format('%s:%s', component_obj.StandardModel,
        component_obj.StandardUniqueIdentifier)
    detail["Status"] = component_obj.Status
    local compare_result = measured_obj.ComparedResult
    return detail, compare_result
end

function m.get_components_info(component_paths)
    local component_infos = cjson.json_object_new_array()
    for _, path in ipairs(component_paths) do
        local detail, compare_result = get_component_detail(path)
        local component_name = string.match(path, "/TPCM/Measured/(%w+)/")
        local system_id = tonumber(string.match(path, '/Systems/(%d+)/'))
        local component_info = nil
        if #component_infos >= 1 then
            for _, comp in ipairs(component_infos) do
                if comp.Name == component_name then
                    component_info = comp
                    break
                end
            end
        end
        if not component_info then
            component_info = cjson.json_object_new_object()
            component_info.Name = component_name
            component_info.SystemId = system_id
            component_info.ComparedResult = 0
            component_info.Details = cjson.json_object_new_array()
            component_infos[#component_infos + 1] = component_info
        end
        if compare_result ~= 0 then
            component_info.ComparedResult = 1
        end
        component_info.Details[#component_info.Details + 1] = detail
    end
    return component_infos
end


function m.get_tpcm_supported(tcm_precense)
    -- 若TCM不在位，则返回false
    if not tcm_precense then
        return false
    end

    local ok, obj = pcall(mdb.get_object, bus, MDB_SERVICE_PATH, MDB_INTF)
    if not ok then
        log:info('Invalid Mdb Path, err: %s', obj)
        return false
    end

    -- TCM在位场景，则需要检查TPCM是否支持
    local ret, valid = pcall(function()
        return obj:IsValidPath(context.get_context(), TPCM_MICROCOMPONENT_PATH)
    end)
    if not ret then
        log:info('Invalid Mdb Method, err: %s', valid)
        return false
    end

    return valid.Result
end

local function get_vm_mdb_paths()
    local ok, rsp = pcall(mdb_service.get_sub_paths, bus, VIRTUALMACHINE_PATH, 1, {VIRTUALMACHINE_MDB_INTF})
    if not ok then
        log:error('Get %s sub paths failed, err: %s', VIRTUALMACHINE_PATH, rsp)
        return {}
    end
    local paths = rsp.SubPaths or {}
    table.sort(paths)
    return paths
end

local function get_vm_array_obj(vm_paths)
    local ok
    local obj
    local vm_array_obj = {}
    for i, path in ipairs(vm_paths) do
        ok, obj = pcall(mdb.get_object, bus, path, VIRTUALMACHINE_MDB_INTF)
        if ok then
            vm_array_obj[path] = obj
        else
            log:error('Invalid Mdb Path %s, err: %s', path, obj)
        end
    end
    return vm_array_obj
end

function m.get_virtual_machines_info()
    local virtual_machines_infos = {}
    local vm_paths = get_vm_mdb_paths()
    local count = #vm_paths
    local vm_array_obj = get_vm_array_obj(vm_paths)

    local measured_types_order = { "BIOS", "SHIM", "GRUB", "OSCfg", "OSKernel", "OSInitrd" }
 
    local measured_types = { ["BIOS"] = "Bios", ["SHIM"] = "Shim", ["GRUB"] = "Grub", ["OSCfg"] = "Grub.Cfg",
    ["OSKernel"] = "Kernel", ["OSInitrd"] = "Initrd" }
    local vm_tpcm_path, ok, vm_tpcm_obj, system_id, vm_id, measured_path, fw_index
    local measured_obj, measured_object_path, ret, measured_object
    for vm_path, virtual_machines_obj in pairs(vm_array_obj) do
        vm_tpcm_path = vm_path .. '/TPCM'
        ok, vm_tpcm_obj = pcall(mdb.get_object, bus, vm_tpcm_path, VIRTUALMACHINE_TPCM_MDB_INTF)
        if not ok then
            log:error('Get %s Mdb object failed, err: %s', vm_tpcm_path, vm_tpcm_obj)
            goto continue
        end

        system_id, vm_id = string.match(vm_path, "/bmc/kepler/Systems/(%d+)/VirtualMachines/([%w_]+)")
        local virtual_machines_info = {}
        virtual_machines_info.Name = virtual_machines_obj.Name
        virtual_machines_info.Id = vm_id
        virtual_machines_info.UUID = virtual_machines_obj.UUID
        virtual_machines_info.MeasureEnabled = vm_tpcm_obj.MeasureEnabled
        virtual_machines_info.MeasureControlAllowed = vm_tpcm_obj.MeasureControlAllowed
        virtual_machines_info.HostingSystemId = tonumber(system_id)
        virtual_machines_info.Components = {}
        measured_path = vm_path .. '/TPCM/Measured/'

        fw_index = 1
        for _, measured_type in ipairs(measured_types_order) do
            measured_obj = measured_types[measured_type]
            measured_object_path = measured_path .. measured_type
            ret, measured_object = pcall(mdb.get_object, bus, measured_object_path, TPCM_MEASURED_MDB_INTF)
            if not ret then
                log:error('Get %s Mdb object failed, err %s', measured_object_path, measured_object)
                goto continue
            end
            local virtual_machines_info_component = {}
            virtual_machines_info_component.MeasuredObject = measured_obj
            virtual_machines_info_component.Version = measured_object.Version
            virtual_machines_info_component.ActualDigest = measured_object.ActualDigest
            virtual_machines_info_component.StandardDigest = measured_object.StandardDigest
            virtual_machines_info_component.vPCR = measured_object.PCRName
            virtual_machines_info_component.IsMatch = measured_object.ComparedResult == 0
            virtual_machines_info.Components[fw_index] = virtual_machines_info_component
            fw_index = fw_index + 1
            ::continue::
        end
        virtual_machines_infos[#virtual_machines_infos + 1] = virtual_machines_info
        ::continue::
    end
    return {count, virtual_machines_infos}
end

return m
